package com.huawei.admins.platform.gateway.core.component.router.locator;

import com.huawei.admins.platform.gateway.core.component.cache.GatewayLocalCache;
import com.huawei.admins.platform.gateway.core.component.filter.GatewayFilterFactory;
import com.huawei.admins.platform.gateway.core.component.predicate.factory.RoutePredicateFactory;
import com.huawei.admins.platform.gateway.core.component.router.RouteDefinition;
import com.huawei.admins.platform.gateway.core.component.router.RouteDefinitionLocator;
import com.huawei.admins.platform.gateway.core.component.router.Router;
import com.huawei.admins.platform.gateway.core.component.router.RouterLocator;
import com.huawei.admins.platform.gateway.core.config.GatewayProperties;
import reactor.core.publisher.Flux;

import java.util.ArrayList;
import java.util.List;

import static com.huawei.admins.platform.gateway.core.util.ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR;

public class RouteDefinitionRouteLocator extends AbstractRouteDefinitionRouteLocator implements RouterLocator {

    private final GatewayProperties gatewayProperties;

    private final RouteDefinitionLocator routeDefinitionLocator;

    private final GatewayLocalCache<List<Router>> routerLocalCache = new GatewayLocalCache<>(60L);

    public RouteDefinitionRouteLocator(GatewayProperties gatewayProperties, RouteDefinitionLocator routeDefinitionLocator,
                                       List<RoutePredicateFactory> predicates, List<GatewayFilterFactory> filterFactories) {
        super(gatewayProperties, predicates, filterFactories);
        this.gatewayProperties = gatewayProperties;
        this.routeDefinitionLocator = routeDefinitionLocator;
    }

    @Override
    public Flux<Router> getRouters() {

        List<Router> routerList = routerLocalCache.get(GATEWAY_ROUTE_ATTR);
        if (routerList != null && !routerList.isEmpty()) {
            return Flux.fromIterable(routerList);
        }
        List<Router> resolvedRouters = new ArrayList<>();
        return getRoutes(this.routeDefinitionLocator.getRouteDefinitions())
                .doOnNext(resolvedRouters::add)
                .doOnComplete(() -> {
                    if (!resolvedRouters.isEmpty()) {
                        routerLocalCache.set(GATEWAY_ROUTE_ATTR, resolvedRouters);
                    }
                });
    }

    private Flux<Router> getRoutes(Flux<RouteDefinition> routeDefinitions) {
        Flux<Router> routes = routeDefinitions.map(this::convertToRoute);

        if (!gatewayProperties.isFailOnRouteDefinitionError()) {
            routes = routes.onErrorContinue((error, obj) -> {
                if (logger.isWarnEnabled()) {
                    logger.warn("RouteDefinition id " + ((RouteDefinition) obj).getId()
                            + " will be ignored. Definition has invalid configs, " + error.getMessage());
                }
            });
        }
        return routes.map(route -> {
            if (logger.isDebugEnabled()) {
                logger.debug("RouteDefinition matched: " + route.getId());
            }
            return route;
        });
    }
}
